Skip to content

agents(os-dev): the one foreground wait shape, step ① on the file surface, one skip-changeset criterion, the anchor's only writer — five cards, one file, 403/403 - #16594

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-16332-os-dev-contract-gaps
Sep 7, 2026
Merged

agents(os-dev): the one foreground wait shape, step ① on the file surface, one skip-changeset criterion, the anchor's only writer — five cards, one file, 403/403#16594
os-zhuang merged 4 commits into
mainfrom
claude/issue-16332-os-dev-contract-gaps

Conversation

@os-musk

@os-musk os-musk commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16332
Fixes #16388
Fixes #13432
Refs #13300
Fixes #16201

Five dev-contract gaps on .claude/agents/os-dev.md, one commit per card, the 403/403 ceiling held (headroom 0): 11 lines changed, 11 removed, and every added line paid by a same-file fold of a restated line (table below). Rules register, every line at or under 120 bytes, no issue numbers in the file, frontmatter and the report JSON field set untouched. Base ffca0df9, head ae47456a. Angle-bracket placeholders are spelled out in this body (PKG) because the platform sanitizer eats them; the file itself keeps the original spelling.

What changed, per card

#16332 — never end the turn for a background notification (commit 17fdc53)

Measured carrier. Old line 341 「形状是 Monitor 带 until 条件,或干脆在前台把那套件跑完。」 named the harness's Monitor as the compliant shape, one line under old 340, which said the only compliant shape is 一次前台阻塞等待. Monitor's own tool contract is a background event stream — "you keep working and notifications arrive in the chat" — which notifies a RUNNING turn; a subagent that ends its turn is reported to its parent as finished and nothing wakes it. All seven recorded final messages name a Monitor or background notification as the wake source, so the seven devs followed old 341 rather than violated it. That line was the carrier to rewrite; a line added beside it would have left two instructions in conflict. Choice: 改写载体, net 0 lines.

  • old 340 + 341 → new 341: 「唯一同时反轮询又不停轮的形状是一次前台阻塞等待:前台跑完,或 tail --pid 等它退出。」
  • new 342, the report-time rule the card asked for: 「比回合长的门禁在写报告那一刻读日志(重定向目标);未完成的记 NOT MEASURED 并写原因。」
  • old 344 → new 345: 「⛔ 永不结束回合去等 Monitor / 后台通知:结束回合即停摆,通知只送达在跑的回合。」 The replaced line 「其它任何收尾文本按定义即停摆;⛔ 不指望后台 monitor 唤醒你,完成通知本身就是停轮。」 keeps its first half at 335 and 344; its second half is now stated as the fact it gestured at.

#16388 — step ① exists only when the diff touches a package (commit d543465)

Premise re-measured on current main. The "build spec" step of #16104 section 3 is today's ①, pnpm --filter 'PKG^...' build, read on a spec card: it builds the package's dependencies, and packages/spec has no workspace dependency, so on a spec card ① runs nothing and exits 0. The file's own wording decides the option: ① is a prerequisite (build the closure before testing and before any gate that reads dist/) which the dispatch-gates derivation at ④ does not perform, so replacing ① with the derivation would delete a real step. Landed: conditioning on the file surface.

#13432 — one skip-changeset criterion (commit 1a1e984)

#13300 — not planned, with the reading (no commit)

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack packages/spec/src/data/filter.zod.ts on ffca0df9 lists 10 of the 14 entries in check-generated.ts's NO_GENERATOR ledger — the audits check:generated prints as deliberately not run — each as its own command: check:empty-state, check:yaml-examples, check:variant-docs, check:duration-unit-keys, check:exported-any, check:dual-source-exports, check:entry-nameability, check:browser-reachable-entries, check:objectui-pin-citations, check:llms-txt (the one that bit in the origin measurement). check:generated itself is never a derived command: --residue places it in the residue, and the tool's own header (line 7680) records why — its path literals are package-root-anchored — and that this is filed separately. So the trigger the card's obligation names ("when check:generated is in the derived family") cannot occur under the derivation the contract already prescribes at ④ with --ran reconciliation: disposition 1 has landed for the audits, and the residue names the rest. Not planned; the seat records this reading on the card. Residue worth knowing: for a schema path check:skill-examples, check:template-manifests, check:error-code-provenance and check:scripts-typecheck are residue entries rather than derived commands — the first two parse against live spec schemas.

#16201 — the anchor's only writer (commit ae47456)

Measured: pnpm --filter @objectstack/spec gen:schema under the verify lock (「VERDICT command-exit 0」) left git status --porcelain empty — authorable-surface.base.json did not move — and the generator's own output names re-anchoring as "a deliberate act with its own reviewed diff — pnpm --filter @objectstack/spec gen:authorable-surface-base". The write in packages/spec/scripts/build-schemas.ts sits under if (UPDATE_BASE) (line 2192). Wording converged on the doc comment PR #16199 gives that file: the writer is the generator "only in its --update-base mode (gen:authorable-surface-base), never on a plain build".

  • old 193 → new 193: 「packages/spec:authorable-surface.base.json 只由 gen:authorable-surface-base 写,普通构建从不写。」
  • 194 untouched, verbatim: 「⛔ 永不回退它、永不为凑某个相等手改它;作数的断言是 check:authorable-surface 绿。」
  • old 195 → new 195: 「baseRev 允许滞后,一行信息不是错误;普通构建下该文件动了是 finding,不是产物。」

Fold table — every added line paid by a deleted restatement

deleted (quoted) paid for where its content survives (new numbering)
old 105 「等锁、门禁批或任何慢步骤一律在本轮内前台阻塞,锁脚本自己会等。」 new 297 (the skip-changeset measurement) 81 一次前台调用领全部等待预算,阻塞到拿锁或 99 · 93 整条流水线在前台跑 · 341 一次前台阻塞等待
old 130 「跳过它产出的失败,读起来与你的改动弄坏了 import 一模一样。」 new 129 (in place) 185–186 判断任何事之前先 build … 假红让你追不存在的问题
old 340 「反轮询与不停轮由同一个形状同时满足,而且只有这一个:一次前台阻塞等待。」 + old 341 「形状是 Monitor 带 until 条件,或干脆在前台把那套件跑完。」 new 342 (the NOT MEASURED rule) 341 (merged; the foreground half survives, the Monitor half was the defect)
old 344 「其它任何收尾文本按定义即停摆;⛔ 不指望后台 monitor 唤醒你,完成通知本身就是停轮。」 new 345 (in place) 335 一轮只在报告交付时结束 · 344 终消息只能是报告 JSON 或 blocked 报告 · 345
old 347 「报告不违反此条:它以结果结束一轮,不是等别的东西来恢复的承诺。」 new 190 (rider C) 335 一轮只在报告交付时结束 · 344

Decision analysis — the two open choices, on the four axes

#16332: rewrite the carrier, or add a line beside it.

  • 实际业务需求: seven measured stalls in 24 h, each a PM round-trip; the stall shape is "armed Monitor, then end the turn", i.e. the devs executed old 341 literally. A line added beside a line that prescribes the failing shape leaves two instructions in conflict.
  • 项目长远合理性: one shape, stated once, matching the harness's real semantics (Monitor is a background stream) — no workaround and no second reading to reconcile later.
  • 防 AI 写代码犯错: the rewrite removes the affordance; new 345 names the tool the devs actually reach for and the fact (通知只送达在跑的回合), so the wrong inference has no premise left.
  • 创业阶段不扩散需求: net 0 lines, no new mechanism.
  • ⇒ rewrite.

#16388: condition ① on the file surface, or replace it with the derivation.

  • 实际业务需求: ① is the prerequisite that tests and every dist/-reading gate need (the closure build); the derivation lists gates and builds nothing, so replacing would remove a real step — the vacuous case is exactly the empty closure, which the condition now names.
  • 项目长远合理性: ④ (what to run) and ① (what must be built first) answer different questions; keeping both, each conditioned, is the sustainable shape.
  • 防 AI 写代码犯错: 「不触任何包则报告无 ①」 makes the no-op visible in the report instead of reading as a green step; 「闭包为空 … 先 build 本包」 closes the spec-card case where every dist/-reading gate would otherwise read a stale build.
  • 创业阶段不扩散需求: net 0 lines.
  • ⇒ condition on the file surface.

Verification (head ae47456a; exit codes captured before any pipe; the gate's own verdict line quoted)

  • pnpm check:pm-skill-ratchet exit 0 — 「✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 403 lines (ceiling 403; headroom 0).」 and 「widest table row is 0 bytes (pin 0; headroom 0).」
  • pnpm check:pm-skill-id-lint exit 0 — 「✓ check-skill-id-lint self-test: 14 cases pass.」
  • pnpm check:skill-frame-sync exit 0 — 「13 cases pass, plus 5 dispatch-gates declaration cases and 3 scan-population cases」
  • pnpm check:nul-bytes exit 0 · pnpm check:agent-model-declared exit 0 (「os-dev.md → opus」) · pnpm check:pm-governed-prose exit 0 · pnpm check:pm-half-states exit 0 (「2636 cases pass」).
  • node scripts/pm/check-governed-merges.mjs --test .claude/agents/os-dev.md exit 3 GOVERNED — 「.claude/** ×1 — the agent instruction tree」.
  • Derivation node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; the changed set from the merge base) → 16 commands, every one run; --ran → 「16 derived, 16 run, 0 NOT-MEASURED, 0 UNRUN」. pnpm --filter @objectstack/lint run check:doc-formula-expressions first answered 「PREREQUISITE NOT MET」 (exit 3, NOT MEASURED, not red) until @objectstack/formula and @objectstack/lint were built under the lock; after both were built under the lock (turbo run build, 「Tasks: 4 successful, 4 total」) the rerun exits 0 — 「✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 434 files / 1371 TS blocks judged clean by @objectstack/formula.」
  • Whole-repo pnpm lint under the verify lock on ae47456a: 「VERDICT command-exit 0 · held the lock 85s」. (eslint's files: populations are all TS/JS globs; the changed .md reports 「File ignored because no matching configuration was supplied」, so the full run is the reading, not a narrowing.)
  • Serial: a driver-less bare clone (merge.os-regen.driver unset, config exit 1) merge-tree --write-tree --name-only ffca0df9 ae47456a returns a clean tree with no conflicting path; all 23 open PR heads (listed after the push) diff 0 lines on this file against their merge base with origin/main.
  • Every changed line at or under 120 bytes (max 120); no control bytes (grep -naP over the drafts exits 1).
  • Changeset: none — .claude/** publishes nothing from any package; skip-changeset applied at open.

验收备注 (observations, not filed)

维护者速读(草稿)

改了什么 — 开发 agent 契约 .claude/agents/os-dev.md 改了 11 行,总行数不变(403/403)。四张卡各落一条规则:① 永不结束回合去等后台通知;跑得比回合长的门禁在写报告那一刻读它的日志,没跑完的记 NOT MEASURED 并写原因。② 「先 build 依赖闭包」这一步只在 diff 触到某个包时才存在;spec 这种没有 workspace 依赖的包,这一步是空跑,读 dist 的门禁前要先 build 本包。③ skip-changeset 只剩一条判据 —— 有没有已发布的东西移动(已发布 = 各包 files[] 实际发运的内容);路径清单降为快速通道,并写明怎么测(构建后 grep,带正控)。④ 纠正一句错话:authorable-surface.base.json 只由 gen:authorable-surface-base 写,普通构建从不写;普通构建下它动了是 finding。第五张卡(#13300)实测后不需要改文件:派生工具已经把那些审计列成独立命令。

为什么改 — 24 小时内七个 dev 因为同一句话停摆,每次都要 PM 手动唤醒;spec 卡上的「构建」步骤什么都没测却读成绿;changeset 标签有两条互相矛盾的判据,dev 每次都得来问;锚点文件那句话把「删除门禁的锚」说成「预期会动的产物」,方向正好反了。

风险与代价(含回滚) — 只改契约文本,不碰代码与发布物,skip-changeset。风险在措辞:若新句子仍被误读,代价是再一轮 PM 唤醒,不影响运行时。回滚 = revert 本 PR 四个 commit 中的任一个,它们互相独立。

席位意见 — (留空)

你要做的 — 受管面(.claude/**),需要你人工合并;读一遍 fold 表,确认被删的五句话的内容都还在。


Generated by Claude Code

… turn for a background notification

Three lines rewritten in place under the 403 ceiling (net 0), all in the
clean-exit section:

- The two shape lines fold into one: the only shape that is both anti-poll and
  non-stop is a single foreground blocking wait — run it to completion in the
  foreground, or block on the job's pid. The old second line named the harness's
  Monitor as the compliant shape; Monitor's own contract is a background event
  stream that notifies a RUNNING turn, so a dev who followed that line armed a
  Monitor and had nothing left to do in the foreground but end the turn.
- The freed line carries the report-time rule: a gate that outlives the turn is
  read from its log (the redirect target) at the moment the report is written,
  and an unfinished one is recorded NOT MEASURED with the reason.
- The "completion notice is itself the stop" tail becomes the explicit fact and
  prohibition: never end the turn to wait for a Monitor or background
  notification — a finished turn is a stopped agent, notifications reach only a
  running turn.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dxq7YqsLDMeZDZ5AzsgJX
…nd is a no-op on an empty closure

Two lines rewritten in place, one rider line added, one restatement folded
(net 0 at 403):

- ① is conditioned on the file surface: the dependency-closure build runs only
  when the diff touches a package; a diff touching none reports that there is
  no ①. On a graph-root package (`packages/spec` has no workspace dependency)
  the closure is empty, so ① runs nothing — gates that read `dist/` need the
  package itself built first. The old "skipping it produces failures that look
  like your change broke an import" tail is the false-red lesson the
  build-before-judging pair already states.
- Rider beside the consumer-sweep lines: a retired key's sweep defaults to the
  retirement playbook's tree-scoped absence pin instead of rebuilding the
  consumer closure for one absence check.
- Folded: "the report does not violate this clause" — the clause's own header
  already says a turn ends only at report delivery.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dxq7YqsLDMeZDZ5AzsgJX
…s — with the path list as its fast path

Two lines rewritten, one measurement line added, one restatement folded
(net 0 at 403):

- The label has one criterion: nothing published moves, where published is what
  each package's `files[]` actually ships. The closed path list is demoted to the
  fast path — those paths publish nothing by definition — and everything else is
  measured: build, then grep the paths `files[]` lists for the symbol with a
  positive control; the symbol at zero hits while the control hits means
  unpublished.
- Folded: "waiting for the lock, a gate batch or any slow step blocks in the
  foreground within the turn; the lock script waits by itself" — stated by the
  foreground-pipeline rule, the lock's single-call budget line and the one
  foreground wait shape.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dxq7YqsLDMeZDZ5AzsgJX
…authorable-surface-base, never by a plain build

Two lines rewritten in place (net 0 at 403); the prohibition line between
them is untouched:

- The causal claim narrows to what build-schemas.ts does: the deletion gate's
  in-tree anchor is written only in `--update-base` mode
  (`gen:authorable-surface-base`), never on a plain build — measured here by a
  plain `gen:schema` run that left `git status` empty.
- Consequence stated where the old clause pointed the other way: on a plain
  build a move of that file is a finding, not an artifact. The "never revert it,
  never hand-edit it, `check:authorable-surface` green is the assertion" line
  survives verbatim.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dxq7YqsLDMeZDZ5AzsgJX
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Sep 7, 2026
@os-musk os-musk added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed documentation Improvements or additions to documentation labels Sep 7, 2026 — with Claude
@os-musk
os-musk requested review from hotlong and os-zhuang September 7, 2026 11:50

os-musk commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

维护者速读(终稿 · skills 席,2026-09-07T11:5xZ)

  • 改了什么:开发 agent 契约 .claude/agents/os-dev.md 改 11 行、删 11 行,总行数不变(403/403),四张卡各一个 commit:① 永不结束回合去等 Monitor / 后台通知(结束回合即停摆,通知只送达在跑的回合);跑得比回合长的门禁在写报告那一刻读日志,没跑完的记 NOT MEASURED 并写原因;原来那句「形状是 Monitor 带 until 条件」删掉 —— 七次停摆的 dev 都是照它做的。② 「先 build 依赖闭包」只在 diff 触到某个包时才存在;spec 这种没有 workspace 依赖的包这一步是空跑,读 dist 的门禁前先 build 本包。③ skip-changeset 只剩一条判据:有没有已发布的东西移动(已发布 = 各包 files[] 实际发运的内容);路径清单降为快速通道;并写明怎么测(构建后 grep,带正控)。④ 纠正一句反了的话:authorable-surface.base.json 只由 gen:authorable-surface-base 写,普通构建从不写;普通构建下它动了是 finding。第五张卡 Make check:generated's "not run here" source-audit list a hard obligation in the os-dev contract — disposition 2 of #13207, routed to the skills lane #13300 实测后不改文件:派生工具已把 check:generated 自报不跑的审计列成独立命令,席位以此关卡。
  • 为什么改:24 小时内七个 dev 因同一句话停摆,每次都要 PM 手动唤醒;spec 卡上「构建闭包」什么都没测却读成绿;changeset 标签有两条互相矛盾的判据,dev 每次都得回来问;锚点那句话把「删除门禁的锚」说成「预期会动的产物」,方向反了。
  • 风险与代价(含回滚):只改契约文本,零代码、零发布物(skip-changeset)。风险在措辞被再次误读,代价是再一轮 PM 唤醒,不影响运行时。回滚 = revert 四个 commit 中任一个,互相独立。
  • 席位意见:席位逐行读了 22 行 diff,在 head 上重数了 403 行、最宽 120 字节、零 issue 编号,对 origin/main b4abb0a9 的 merge-tree 干净;五处 fold 的幸存位置逐条核过(被删的都是复述句,规则内容都还在 PR 正文表格点名的行上);Make check:generated's "not run here" source-audit list a hard obligation in the os-dev contract — disposition 2 of #13207, routed to the skills lane #13300 的读数在本席检出上复现(spec zod 路径派生 63 条命令,含 check:llms-txt;check:generated 不在其中)。CI 31 项 11:49Z 无红,四项仍在跑。建议直接合并。
  • 你要做的:读一遍 PR 正文的 fold 表,确认被删的五句话内容都还在,然后人工合并(受管面不进合并队列)。

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment